Skip to content

feat: add logit-normal scheduler#1669

Open
stduhpf wants to merge 6 commits into
leejet:masterfrom
stduhpf:logit-normal
Open

feat: add logit-normal scheduler#1669
stduhpf wants to merge 6 commits into
leejet:masterfrom
stduhpf:logit-normal

Conversation

@stduhpf

@stduhpf stduhpf commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for the logit-normal schedule that supposed to be the default noise schedule for Ideogram4 (see https://github.com/ideogram-oss/ideogram4/blob/main/docs/pipeline.md#noise-schedule).

It's now set as default scheduler when running an Ideogram4 model.

Related Issue / Discussion

N/A

Additional Information

Uses Acklam's Algorithm to approximate the Inverse Normal CDF to avoid adding a boost dependency.

This scheduler has two tweakable parameters: muand std. Here are the values recommended for Ideogram (source: https://github.com/ideogram-oss/ideogram4/blob/main/src/ideogram4/sampler_configs.py)

  • 48 steps: mu=0.0, std = 1.5
  • 20 steps: mu=0.0, std = 1.75 (default)
  • 12 steps: mu=0.5, std = 1.75

They are set via the --extra-sample-args parameter. For example, for 12 steps generations, you should add --extra-sample-args "mu=0.5,std=1.75" to the cli args.

12 steps

Discrete scheduler Logit_normal scheduler (mu=0.5,std=1.75)
output output

20 steps

Discrete scheduler Logit_normal scheduler (mu=0,std=1.75)
output 1-l_n(1-x)

48 steps

Discrete scheduler Logit_normal scheduler (mu=0,std=1.5)
output - Copy (344) output - Copy (345)

Checklist

@stduhpf

stduhpf commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

I played around with the formula a bit, by carefully chosing the mean and std parameters, it seems to be possible to approximate roughly any other schedule...

Examples:

mean std closest schedule
0 1.7 discrete
0 2.6 smoothstep
-3.3 2.6 exponential

https://www.desmos.com/calculator/t8ji3ml3aq

For some reason, the recommended parameters seem to give subpar results compared to discrete (the current default), especially at high resolution (at 512x512 is should be pretty close but the resolution-aware thing steers it away at higher resolution).

Maybe it's just that I'm comparing with prompts/seeds that were cherry-picked because they were working great with the discrete scheduler.

@stduhpf stduhpf force-pushed the logit-normal branch 3 times, most recently from 6bc8091 to 9dd0471 Compare June 19, 2026 16:16
@stduhpf stduhpf marked this pull request as ready for review June 19, 2026 16:33
@stduhpf

stduhpf commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

@leejet @Green-Sky @rmatif @daniandtheweb or anyone who actualy understand this stuff, could you take a look?

I'm a bit confused by some things, like why is t_to_sigma not the inverse of sigma_to_t for Flow denoisers, and why some schedulers ignore t_to_sigma but others use it etc...

@stduhpf

stduhpf commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

https://github.com/Comfy-Org/ComfyUI/blob/master/comfy_extras/nodes_ideogram4.py

Looks like they flip the order and do 1-x here, I will try that and see how it looks lilke.

Edit: yeah, that's it. I must have missed something when looking at the original reference implementation.

@stduhpf stduhpf force-pushed the logit-normal branch 2 times, most recently from 4bc40ce to f4a1966 Compare June 20, 2026 14:30
Comment thread src/runtime/denoiser.hpp Outdated
Comment on lines +750 to +754
// which one is corrrect ?
float sigma = timestep;
// float sigma = t_to_sigma(timestep * TIMESTEPS);
// float sigma = t_to_sigma(timestep * (TIMESTEPS - 1));
// float sigma = t_to_sigma(timestep * TIMESTEPS - 1);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR is pretty much ready to merge, only thing I'd still like a second opinion on is part.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know in flow denoisers time = noise so there should be no need to be using any t_to_sigma to calculate the values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's only true if shift is set to 1

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use this directly:

float sigma = timestep;

For this logit-normal scheduler, timestep is already the normalized flow sigma produced by the reference schedule. The original Ideogram4 implementation does not apply an additional flow-shift parameter here, so running it through t_to_sigma() would introduce behavior that is not part of the reference schedule.

If we did want to convert through t_to_sigma, the only consistent form would be:

float sigma = t_to_sigma(timestep * TIMESTEPS - 1);

because DiscreteFlowDenoiser::t_to_sigma() already adds +1 internally. With flow_shift = 1.0, that is effectively equivalent to float sigma = timestep. But for this scheduler, I would keep it as:

float sigma = timestep;

@stduhpf stduhpf mentioned this pull request Jun 21, 2026
1 task
@stduhpf stduhpf changed the title feat: add logit-normal schedule feat: add logit-normal scheduler Jun 21, 2026
@daniandtheweb

Copy link
Copy Markdown
Contributor

@leejet @Green-Sky @rmatif @daniandtheweb or anyone who actualy understand this stuff, could you take a look?

I'm a bit confused by some things, like why is t_to_sigma not the inverse of sigma_to_t for Flow denoisers, and why some schedulers ignore t_to_sigma but others use it etc...

It may not be the best explanation as I'm not really an expert on it but I'll try.

For your first question t_to_sigma is not the inverse of sigma_to_t for flow denoisers because of the need to add a shift during the denoising phase. If they were exact inverses they'd cancel the shift out.

About t_to_sigma being ignored by some schedulers I think it's mostly because some schedulers need to lookup the noise level while others calculate the noise curve independently.

@stduhpf

stduhpf commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

For your first question t_to_sigma is not the inverse of sigma_to_t for flow denoisers because of the need to add a shift during the denoising phase. If they were exact inverses they'd cancel the shift out.

I see, but even when there is no shifting, there's an off-by-one difference. I guess it's close enough?

some schedulers need to lookup the noise level while others calculate the noise curve independently.

Hmm. When the scheduler is calculating the noise curve independantly, doesn't it break compatibility with the different kinds of denoisers (or settings like flow shift)?

@wbruna

wbruna commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

I had notice that difference before (#1372). Note they are called in very different contexts; I suspect they are not really intended to be inverses of each other, despite their names.

or settings like flow shift

Hm. Yeah, I believe the schedulers should always call t_to_sigma, at least to apply the shift.

@daniandtheweb

Copy link
Copy Markdown
Contributor

I see, but even when there is no shifting, there's an off-by-one difference. I guess it's close enough?

From what I understand: yes, it's close enough.

Hmm. When the scheduler is calculating the noise curve independantly, doesn't it break compatibility with the different kinds of denoisers (or settings like flow shift)?

I haven't tested it but in theory not all schedulers should work for flow models.

@leejet

leejet commented Jun 22, 2026

Copy link
Copy Markdown
Owner

t_to_sigma() mainly exists for schedulers that operate in timestep space. For example, the discrete scheduler samples linear timestep indices, and then asks the denoiser to map those timesteps to actual sigma values.

For flow denoisers, this is also where the flow shift is applied: t_to_sigma() takes the linear normalized value and applies the shift to produce the final sigma.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants